package by.artureg.so.dl;

import javax.sql.DataSource;

import by.artureg.so.conf.Const;
import by.artureg.so.conf.Field;
import by.artureg.so.conf.Parameter;
import by.artureg.so.conf.Report;
import by.artureg.so.ex.SoException;
import by.artureg.so.to.ColumnTo;
import by.artureg.so.to.ParamTo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Service;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;

@Service
public class SoDao extends JdbcDaoSupport {
    private static final Logger log = LoggerFactory.getLogger(SoDao.class);
    private NamedParameterJdbcTemplate template;


    @Autowired
    private void setDS(DataSource dataSource) throws SoException {
        this.setDataSource(dataSource);
        this.template = new NamedParameterJdbcTemplate(dataSource);
    }

    @SuppressWarnings("unchecked")
    public List<List<ColumnTo>> request(final Report report, List<ParamTo> values) throws SoException {
        log.debug("Saving request: '{}'", report);


        MapSqlParameterSource namedParameters
                = new MapSqlParameterSource();
        for (ParamTo value : values) {
            Parameter parameter = report.getParamByName(value.getName());

            String type = parameter.getType() != null ? parameter.getType().toLowerCase() : Const.STRING;

            try {
                if (Const.INTEGER.equals(type)) {
                    namedParameters.addValue(value.getName(), Integer.valueOf(value.getValue()));
                } else if (Const.STRING.equals(type)) {
                    namedParameters.addValue(value.getName(), value.getValue());
                } else if (Const.BOOL.equals(type)) {
                    namedParameters.addValue(value.getName(), Boolean.valueOf(value.getValue()));
                } else if (Const.DATE.equals(type) || Const.DATETIME.equals(type)) {
                    namedParameters.addValue(value.getName(), new Date(Long.valueOf(value.getValue())));
                } else if (Const.DECIMAL.equals(type)) {
                    //namedParameters.addValue(value.getName(), .valueOf(value.getValue()));
                } else if (Const.DOUBLE.equals(type)) {
                    namedParameters.addValue(value.getName(), Double.valueOf(value.getValue()));
                } else {
                    throw new SoException("Unknown parameter type: " + type);
                }
            } catch (NumberFormatException e) {
                throw new SoException("Incorrect number format");
            }

        }

        return template.query(
                report.getSql(),
                namedParameters,
                new RowMapper<List<ColumnTo>>() {
                    public List<ColumnTo> mapRow(ResultSet rs, int i) throws SQLException {
                        List<ColumnTo> result = new ArrayList<ColumnTo>();

                        ResultSetMetaData rsmd = rs.getMetaData();
                        int numColumns = rsmd.getColumnCount();

                        for (int j = 1; j <= numColumns; j++) {
                            String columnName = rsmd.getColumnName(j);
                            Field f = report.getFieldByName(columnName);

                            if (f == null || !"n".equals(f.getVisible())) {
                                ColumnTo to = new ColumnTo();
                                to.setLabel(f != null && f.getLabel() != null ? f.getLabel() : columnName);

                                Object value = rs.getObject(j);
                                if (f != null && f.getFormat() != null && value instanceof Date) {
                                    to.setValue(new SimpleDateFormat(f.getFormat()).format(value));
                                } else {
                                    to.setValue(value);
                                }

                                result.add(to);
                            }
                        }

                        return result;
                    }
                });
    }


}